<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
    />
    <meta
      name="description"
      content="Explore node transformations of 3D models."
    />
    <meta name="cesium-sandcastle-labels" content="Development" />
    <title>Cesium Demo</title>
    <script type="text/javascript" src="../Sandcastle-header.js"></script>
    <script
      type="text/javascript"
      src="../../../Build/CesiumUnminified/Cesium.js"
      nomodule
    ></script>
    <script type="module" src="../load-cesium-es6.js"></script>
  </head>
  <body
    class="sandcastle-loading"
    data-sandcastle-bucket="bucket-requirejs.html"
  >
    <style>
      @import url(../templates/bucket.css);
      #toolbar {
        background: rgba(42, 42, 42, 0.8);
        padding: 4px;
        border-radius: 4px;
      }
      #toolbar input {
        vertical-align: middle;
        padding-top: 2px;
        padding-bottom: 2px;
      }
    </style>
    <div id="cesiumContainer" class="fullSize"></div>
    <div id="loadingOverlay"><h1>Loading...</h1></div>
    <div id="toolbar">
      <table>
        <tbody>
          <tr>
            <td
              colspan="2"
              data-bind="click: function() { showTranslation = !showTranslation }"
            >
              <span data-bind="text: showTranslation ? '-' : '+'">+</span>
              Translation
            </td>
          </tr>
          <tr data-bind="visible: showTranslation">
            <td>X</td>
            <td>
              <input
                type="range"
                min="-3"
                max="3"
                step="0.01"
                data-bind="value: translationX, valueUpdate: 'input'"
              />
              <input type="text" size="2" data-bind="value: translationX" />
            </td>
          </tr>
          <tr data-bind="visible: showTranslation">
            <td>Y</td>
            <td>
              <input
                type="range"
                min="-3"
                max="3"
                step="0.01"
                data-bind="value: translationY, valueUpdate: 'input'"
              />
              <input type="text" size="2" data-bind="value: translationY" />
            </td>
          </tr>
          <tr data-bind="visible: showTranslation">
            <td>Z</td>
            <td>
              <input
                type="range"
                min="-3"
                max="3"
                step="0.01"
                data-bind="value: translationZ, valueUpdate: 'input'"
              />
              <input type="text" size="2" data-bind="value: translationZ" />
            </td>
          </tr>
          <tr>
            <td
              colspan="2"
              data-bind="click: function() { showRotation = !showRotation }"
            >
              <span data-bind="text: showRotation ? '-' : '+'">+</span> Rotation
            </td>
          </tr>
          <tr data-bind="visible: showRotation">
            <td>H</td>
            <td>
              <input
                type="range"
                min="-3"
                max="3"
                step="0.01"
                data-bind="value: rotationHeading, valueUpdate: 'input'"
              />
              <input type="text" size="2" data-bind="value: rotationHeading" />
            </td>
          </tr>
          <tr data-bind="visible: showRotation">
            <td>P</td>
            <td>
              <input
                type="range"
                min="-3"
                max="3"
                step="0.01"
                data-bind="value: rotationPitch, valueUpdate: 'input'"
              />
              <input type="text" size="2" data-bind="value: rotationPitch" />
            </td>
          </tr>
          <tr data-bind="visible: showRotation">
            <td>R</td>
            <td>
              <input
                type="range"
                min="-3"
                max="3"
                step="0.01"
                data-bind="value: rotationRoll, valueUpdate: 'input'"
              />
              <input type="text" size="2" data-bind="value: rotationRoll" />
            </td>
          </tr>
          <tr>
            <td
              colspan="2"
              data-bind="click: function() { showScale = !showScale }"
            >
              <span data-bind="text: showScale ? '-' : '+'">+</span> Scale
            </td>
          </tr>
          <tr data-bind="visible: showScale">
            <td>X</td>
            <td>
              <input
                type="range"
                min="0.01"
                max="3"
                step="0.01"
                data-bind="value: scaleX, valueUpdate: 'input'"
              />
              <input type="text" size="2" data-bind="value: scaleX" />
            </td>
          </tr>
          <tr data-bind="visible: showScale">
            <td>Y</td>
            <td>
              <input
                type="range"
                min="0.01"
                max="3"
                step="0.01"
                data-bind="value: scaleY, valueUpdate: 'input'"
              />
              <input type="text" size="2" data-bind="value: scaleY" />
            </td>
          </tr>
          <tr data-bind="visible: showScale">
            <td>Z</td>
            <td>
              <input
                type="range"
                min="0.01"
                max="3"
                step="0.01"
                data-bind="value: scaleZ, valueUpdate: 'input'"
              />
              <input type="text" size="2" data-bind="value: scaleZ" />
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    <script id="cesium_sandcastle_script">
      function startup(Cesium) {
        "use strict";
        //Sandcastle_Begin
        // this can be changed to any glTF model
        const modelUrl = "../../SampleData/models/CesiumMan/Cesium_Man.glb";

        const viewModel = {
          nodeName: undefined,
          showTranslation: false,
          showRotation: false,
          showScale: false,
          transformations: {},
        };

        Cesium.knockout.track(viewModel);

        // transformation is a computed property returning the values storage for the current node name
        Cesium.knockout.defineProperty(
          viewModel,
          "transformation",
          function () {
            const transformations = viewModel.transformations;
            const nodeName = viewModel.nodeName;
            if (!Cesium.defined(transformations[nodeName])) {
              transformations[nodeName] = {
                translationX: 0.0,
                translationY: 0.0,
                translationZ: 0.0,
                rotationHeading: 0.0,
                rotationPitch: 0.0,
                rotationRoll: 0.0,
                scaleX: 1.0,
                scaleY: 1.0,
                scaleZ: 1.0,
              };
              Cesium.knockout.track(transformations[nodeName]);
            }
            return transformations[nodeName];
          }
        );

        // these writable computed properties produce individual values for use in the UI
        [
          "translationX",
          "translationY",
          "translationZ",
          "rotationHeading",
          "rotationPitch",
          "rotationRoll",
          "scaleX",
          "scaleY",
          "scaleZ",
        ].forEach(function (p) {
          Cesium.knockout.defineProperty(viewModel, p, {
            get: function () {
              return viewModel.transformation[p];
            },
            set: function (value) {
              // coerce values to number
              viewModel.transformation[p] = +value;
            },
          });
        });

        // these computed properties return each element of the transform
        Cesium.knockout.defineProperty(viewModel, "translation", function () {
          return new Cesium.Cartesian3(
            viewModel.translationX,
            viewModel.translationY,
            viewModel.translationZ
          );
        });
        Cesium.knockout.defineProperty(viewModel, "rotation", function () {
          const hpr = new Cesium.HeadingPitchRoll(
            viewModel.rotationHeading,
            viewModel.rotationPitch,
            viewModel.rotationRoll
          );
          return Cesium.Quaternion.fromHeadingPitchRoll(hpr);
        });
        Cesium.knockout.defineProperty(viewModel, "scale", function () {
          return new Cesium.Cartesian3(
            viewModel.scaleX,
            viewModel.scaleY,
            viewModel.scaleZ
          );
        });
        // this computed property combines the above properties into a single matrix to be applied to the node
        Cesium.knockout.defineProperty(viewModel, "matrix", function () {
          return Cesium.Matrix4.fromTranslationQuaternionRotationScale(
            viewModel.translation,
            viewModel.rotation,
            viewModel.scale
          );
        });

        const toolbar = document.getElementById("toolbar");
        Cesium.knockout.applyBindings(viewModel, toolbar);

        const viewer = new Cesium.Viewer("cesiumContainer");
        const scene = viewer.scene;

        const height = 250000.0;
        const origin = Cesium.Cartesian3.fromDegrees(
          -123.0744619,
          44.0503706,
          height
        );
        const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(
          origin,
          new Cesium.HeadingPitchRoll()
        );

        const model = scene.primitives.add(
          Cesium.Model.fromGltf({
            url: modelUrl,
            modelMatrix: modelMatrix,
            minimumPixelSize: 128,
          })
        );

        model.readyPromise
          .then(function (model) {
            const camera = viewer.camera;

            // Zoom to model
            const controller = scene.screenSpaceCameraController;
            const r =
              2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near);
            controller.minimumZoomDistance = r * 0.5;

            const center = Cesium.Matrix4.multiplyByPoint(
              model.modelMatrix,
              model.boundingSphere.center,
              new Cesium.Cartesian3()
            );
            const heading = Cesium.Math.toRadians(230.0);
            const pitch = Cesium.Math.toRadians(-20.0);
            camera.lookAt(
              center,
              new Cesium.HeadingPitchRange(heading, pitch, r * 2.0)
            );

            // enumerate nodes and add options
            const options = Object.keys(model._runtime.nodesByName).map(
              function (nodeName) {
                return {
                  text: nodeName,
                  onselect: function () {
                    viewModel.nodeName = nodeName;
                  },
                };
              }
            );
            options[0].onselect();
            Sandcastle.addToolbarMenu(options);

            // respond to viewmodel changes by applying the computed matrix
            Cesium.knockout
              .getObservable(viewModel, "matrix")
              .subscribe(function (newValue) {
                const node = model.getNode(viewModel.nodeName);
                if (!Cesium.defined(node.originalMatrix)) {
                  node.originalMatrix = node.matrix.clone();
                }
                node.matrix = Cesium.Matrix4.multiply(
                  node.originalMatrix,
                  newValue,
                  new Cesium.Matrix4()
                );
              });
          })
          .catch(function (error) {
            window.alert(error);
          });

        //Sandcastle_End
        Sandcastle.finishedLoading();
      }
      if (typeof Cesium !== "undefined") {
        window.startupCalled = true;
        startup(Cesium);
      }
    </script>
  </body>
</html>
